﻿#include <iostream>
using std::cout;
using std::endl;

// 1. 多态的概念
// 1.1 概念
// 多态的概念: 通俗来说, 就是多种形态
// 具体点就是去完成某个行为, 当不同的对象去完成时会产生出不同的状态
// 举例: 比如买票这个行为
// 当普通人买票时, 是全价买票
// 学生买票时, 是半价买票

// 2. 多态的定义及实现
// 2.1 多态的构成条件
// 多态是在继承关系不同位置的类对象 
// 去调用同一函数, 产生了不同的行为
// 比如 Student 继承了 Person
// Person 对象买票全价, Student 对象买票半价
// 那么在继承中要构成多态还有两个条件:
// 1. 必须通过基类的指针或者引用调用虚函数
// 2. 被调用的函数必须是虚函数, 且派生类必须对基类的虚函数进行重写

// 注意: 对于 1. 为什么不能是子类指针或者引用, 为什么不能是父类对象?
// 子类赋值给父类对象切片, 不会拷贝虚函数表, 如果拷贝虚函数表, 
// 那么对于在子类中进行了重写的虚函数
// 父类对象的虚函数表中存的是父类虚函数的地址还是子类虚函数的地址就不确定了.
 
//class Person
//{
//public:
//	virtual void BuyTicket()
//	{
//		cout << "买票全价" << endl;
//	}
//};
//
//class Student : public Person
//{
//	virtual void BuyTicket()
//	{
//		cout << "买票半价" << endl;
//	}
//};
//
//void Func(Person& people)
//{
//	people.BuyTicket();
//}
//
//int main()
//{
//	Person ps;
//	Func(ps);
//
//	Student st;
//	Func(st);
//
//	return 0;
//}

// 2.2 虚函数
// 虚函数: 被 virtual 修饰的类成员函数称为虚函数

//class Person 
//{
//public:
//	virtual void BuyTicket() 
//	{
//		cout << "买票-全价" << endl;
//	}
//};

// 2.3 虚函数的重写
// 虚函数的重写 (覆盖): 派生类中有一个跟基类完全相同的虚函数
// 三同 (即派生类虚函数与基类虚函数的返回值类型, 函数名字, 参数列表完全相同)
// 称子类的虚函数重写了基类的虚函数

//class Person
//{
//public:
//	virtual void BuyTicket() 
//	{ 
//		cout << "买票-全价" << endl; 
//	}
//};
//
//class Student : public Person 
//{
//public:
//	virtual void BuyTicket()
//	{
//		cout << "买票-半价" << endl;
//	}
//	/*注意: 在重写基类虚函数时, 派生类的虚函数在不加 virtual 关键字时
//	虽然也可以构成重写 (因为继承后基类的虚函数被继承下来了在派生类依旧保持虚函数属性)
//	但是该种写法不是很规范, 不建议这样使用*/
//	/*void BuyTicket() { cout << "买票-半价" << endl; }*/
//};
//
//void Func(Person& p)
//{
//	p.BuyTicket();
//}
//
//int main()
//{
//	Person ps;
//	Student st;
//
//	Func(ps);
//	Func(st);
//
//	return 0;
//}

// 虚函数重写的两个例外:
// 1. 协变 (基类与派生类虚函数返回值类型不同)
// 派生类重写基类虚函数时, 与基类虚函数返回值类型不同
// 即基类虚函数返回基类对象的指针或者引用
// 派生类虚函数返回派生类对象的指针或者引用时, 称为协变

//class A 
//{
//};
//
//class B : public A 
//{
//};
//
//class Person 
//{
//public:
//	virtual A* f() 
//	{
//		return new A;
//	}
//};
//
//class Student : public Person 
//{
//public:
//	virtual B* f() 
//	{ 
//		return new B;
//	}
//};
//
//int main()
//{
//	return 0;
//}

// 2. 析构函数的重写 (基类与派生类析构函数的名字不同)
// 如果基类的析构函数为虚函数, 此时派生类析构函数只要定义
// 无论是否加 virtual 关键字, 都与基类的析构函数构成重写
// 虽然基类与派生类析构函数名字不同 (函数名不相同)
// 看起来违背了重写的规则, 其实不然
// 这里可以理解为编译器对析构函数的名称做了特殊处理
// 编译后析构函数的名称统一处理成 destructor

//class Person 
//{
//public:
//	virtual ~Person() 
//	{
//		cout << "~Person()" << endl;
//	}
//};
//
//class Student : public Person 
//{
//public:
//	virtual ~Student()
//	{ 
//		cout << "~Student()" << endl;
//	}
//};

// 只有派生类 Student 的析构函数重写了基类 Person 的析构函数
// 下面 delete 对象调用析构函数时, 才能构成多态
// 才能保证 p1 和 p2 指向的对象正确地调用析构函数

//int main()
//{
//	Person* p1 = new Person;
//	Person* p2 = new Student;
//
//	delete p1;
//	delete p2;
//
//	return 0;
//}

// 2.4 C++ 11 override 和 final
// 从上面可以看出, C++ 对函数重写的要求比较严格
// 但是有些情况下由于疏忽, 可能会导致函数名字母次序写反而无法构成重载
// 而这种错误在编译期间是不会报出的, 只有在程序运行时没有
// 得到预期结果才来 debug 会得不偿失, 因此
// C++11 提供了 override 和 final 两个关键字
// 可以帮助用户检测是否重写

// 1. final: 修饰虚函数, 表示该虚函数不能再被重写

//class Car
//{
//public:
//	virtual void Drive() final 
//	{
//	}
//};
//
//class Benz :public Car
//{
//public:
//	virtual void Drive()	// error
//	{ 
//		cout << "Benz-舒适" << endl;
//	}
//};

// 2. override: 检查派生类虚函数是否重写了基类某个虚函数, 如果没有重写编译报错

//class Car 
//{
//public:
//	virtual void Drive() 
//	{
//	}
//};
//
//class Benz :public Car 
//{
//public:
//	virtual void Drive() override 
//	{
//		cout << "Benz-舒适" << endl; 
//	}
//};
//
//int main()
//{
//	return 0;
//}

// 2.5 重载, 覆盖(重写), 隐藏(重定义)的对比

// 重载
// 两个函数在同一作用域
// 函数名相同/参数不同

// 重写(覆盖)
// 两个函数分别在基类和派生类的作用域
// 函数名/参数/返回值类型都必须相同(协变例外)
// 两个函数必须是虚函数

// 重定义(隐藏)
// 两个函数分别在基类和派生类的作用域
// 函数名相同
// 两个基类和派生类的同名函数不构成重写就是重定义

// 3. 抽象类
// 3.1 概念
// 在虚函数的后面写上 = 0, 则这个函数为纯虚函数
// 包含纯虚函数的类叫做抽象类 (也叫接口类), 
// 抽象类不能实例化出对象, 派生类继承后也不能实例化出对象
// 只有重写纯虚函数, 派生类才能实例化出对象
// 纯虚函数规范了派生类必须重写, 另外纯虚函数更体现出了接口继承

//class Car
//{
//public:
//	virtual void Drive() = 0;
//};
//
//class Benz : public Car
//{
//public:
//	virtual void Drive()
//	{
//		cout << "Benz-舒适" << endl;
//	}
//};
//
//class BMW : public Car
//{
//public:
//	virtual void Drive()
//	{
//		cout << "BMW-操控" << endl;
//	}
//};
//
//int main()
//{
//	Car* pBenz = new Benz;
//	pBenz->Drive();
//
//	Car* pBMW = new BMW;
//	pBMW->Drive();
//
//	return 0;
//}

// 3.2 接口继承和实现继承
// 普通函数的继承是一种实现继承, 派生类继承了基类函数
// 可以使用函数, 继承的是函数的实现
// 虚函数的继承是一种接口继承, 派生类继承的是基类虚函数的接口
// 目的是为了重写, 达成多态, 继承的是接口
// 所以如果不实现多态, 就不要把函数定义成虚函数

// 4. 多态的原理
// 4.1 虚函数表

// 这里常考一道笔试题: sizeof(Base)是多少?
//class Base
//{
//public:
//	virtual void Func1()
//	{
//		cout << "Func1()" << endl;
//	}
//private:
//	int _b = 1;
//};
//
//int main()
//{
//	Base b;
//	cout << sizeof(b) << endl;	// 8 (x86)
//	cout << sizeof(int) << endl;	// 4
//	cout << sizeof(void**) << endl;	// 4
//	return 0;
//}

// 通过观察测试发现 b 对象是 8bytes, 除了 _b 成员, 还多一个 __vfptr 放在对象的前面
// (注意有些平台可能会放到对象的最后面, 这个跟平台有关), 对象中的这个指针叫做虚函数表指针 (类型为 void**)
// (v 代表 virtual, f 代表 function). 一个含有虚函数的类中至少有一个虚函数表指针
// 因为虚函数的地址要被放到虚函数表中, 虚函数表也简称虚表
// 那么派生类中这个表放了些什么呢? 接着往下分析.

// 针对上面的代码我们做出以下改造
// 1. 我们增加一个派生类 Derive 去继承 Base
// 2. Derive 中重写 Func1
// 3. Base 再增加一个虚函数 Func2 和一个普通函数 Func3

//class Base
//{
//public:
//	virtual void Func1()
//	{
//		cout << "Base::Func1()" << endl;
//	}
//	virtual void Func2()
//	{
//		cout << "Base::Func2()" << endl;
//	}
//	void Func3()
//	{
//		cout << "Base::Func3()" << endl;
//	}
//private:
//	int _b = 1;
//};
//
//class Derive : public Base
//{
//public:
//	virtual void Func1()
//	{
//		cout << "Derive::Func1()" << endl;
//	}
//private:
//	int _d = 2;
//};
//
//int main()
//{
//	Base b;
//	cout << sizeof(b) << endl;	// 8
//
//	Derive d;
//	cout << sizeof(d) << endl;	// 12
//
//	return 0;
//}

// 通过观察和测试, 我们发现了以下几点问题:
// 1. 派生类对象 d 中也有一个虚表指针, d 对象由两部分构成
// 一部分是父类继承下来的成员, 虚表指针 + _b , 另一部分是自己的成员(_d)
// 2. 基类 b 对象和派生类 d 对象虚表是不一样的
// 我们发现 Func1 完成了重写, 所以 d 的虚表中存的是重写的 Derive::Func1d 的地址
// 所以虚函数的重写也叫作覆盖, 覆盖就是指虚表中虚函数地址的覆盖
// 重写是语法层的叫法, 覆盖是原理层的叫法
// 3. 另外 Func2 继承下来后是虚函数, 所以其地址放进了虚表
// Func3 虽然也继承下来了, 但是不是虚函数, 所以其地址不会放进虚表
// 4. 虚函数表本质是一个存虚函数指针的指针数组, 一般情况这个数组最后面放了一个 nullptr
// 5. 总结一下派生类的虚表生成: a. 先将基类中的虚表内容拷贝一份到派生类虚表中
// b. 如果派生类重写了基类中某个虚函数, 用派生类自己的虚函数的地址覆盖虚表中基类的虚函数的地址
// c. 派生类自己新增加的虚函数的地址按其在派生类中的声明次序增加到派生类虚表的最后
// 6. 这里还有一个很容易混淆的问题: 虚函数存在哪? 虚表存在?
// 答: 虚函数存在虚表中, 虚表存在对象中. 注意上面的回答是错的
// 虚表存的是虚函数指针, 不是虚函数, 虚函数和普通函数是一样的
// 都是存在代码段 (Text Segment) 的, 只是它的指针又存到了虚表中
// 另外对象中存的不是虚表, 存的是虚表指针. 那么虚表存在哪的呢?
// 实际我们去验证一下会发现 vs 下是存在代码段的, Linux g++ 下不一定.

//class Person
//{
//public:
//	virtual void BuyTicket() { cout << "买票-全价" << endl; }
//
//	virtual void Func1()
//	{
//		cout << "Person::Func1()" << endl;
//	}
//
//	virtual void Func2()
//	{
//		cout << "Person::Func2()" << endl;
//	}
//
//protected:
//	int _a = 0;
//};
//
//class Student : public Person 
//{
//public:
//	virtual void BuyTicket() { cout << "买票-半价" << endl; }
//
//	virtual void Func3()
//	{
//		cout << "Student::Func3()" << endl;
//	}
//
//protected:
//	int _b = 1;
//};
//
//int main()
//{
//	Person ps;
//	Student st;
//
//	int a = 0;
//	printf("栈: 0x%p\n", &a);
//
//	static int b = 0;
//	printf("静态区: 0x%p\n", &b);
//
//	int* p = new int;
//	printf("堆: 0x%p\n", p);
//
//	const char* str = "hello world";
//	printf("常量区: 0x%p\n", str);
//
//	printf("虚表1: 0x%p\n", *((void**)&ps));
//	printf("虚表2: 0x%p\n", *((void**)&st));
//
//	return 0;
//}

//栈: 0x010FFAEC
//静态区 : 0x0099D434
//堆 : 0x0129F0F8
//常量区 : 0x0099ADB4
//虚表1 : 0x0099AB34
//虚表2 : 0x0099AB70

// 同类型的对象共用一张虚函数表
//class Person
//{
//public:
//	virtual void BuyTicket() { cout << "买票-全价" << endl; }
//
//	virtual void Func1()
//	{
//		cout << "Person::Func1()" << endl;
//	}
//
//	virtual void Func2()
//	{
//		cout << "Person::Func2()" << endl;
//	}
//
//protected:
//	int _a = 0;
//};
//
//class Student : public Person
//{
//public:
//	virtual void BuyTicket() { cout << "买票-半价" << endl; }
//
//	virtual void Func3()
//	{
//		cout << "Student::Func3()" << endl;
//	}
//protected:
//	int _b = 1;
//};
//
//int main()
//{
//	Person ps1;
//	Student st1;
//
//	Person ps2;
//	Student st2;
//
//	return 0;
//}

// 4.2 多态的原理
// 上面分析了这个半天了那么多态的原理到底是什么? 还记得这里
// Func 函数传 Person 调用的 Person::BuyTicket
// 传 Student 调用的是 Student::BuyTicket

//class Person 
//{
//public:
//	virtual void BuyTicket() 
//	{ 
//		cout << "买票-全价" << endl; 
//	}
//};
//
//class Student : public Person 
//{
//public:
//	virtual void BuyTicket() 
//	{ 
//		cout << "买票-半价" << endl; 
//	}
//};
//
//void Func(Person* p)
//{
//	p->BuyTicket();
//}
//
//int main()
//{
//	Person Mike;
//	Func(&Mike);
//
//	Student Johnson;
//	Func(&Johnson);
//
//	return 0;
//}

//void Func(Person* p)
//{
//	p->BuyTicket();
//}
//int main()
//{
//	Person mike;
//	Func(&mike);
//	mike.BuyTicket();
//
//	return 0;
//}

// 1. 观察下图的红色箭头我们看到, p 是指向 mike 对象时
// p->BuyTicket 在 mike 的虚表中找到虚函数是 Person::BuyTicket
// 2. 观察下图的蓝色箭头我们看到, p 是指向 johnson 对象时
// p->BuyTicket 在 johson 的虚表中找到虚函数是 Student::BuyTicket
// 3. 这样就实现出了不同对象去完成同一行为时, 展现出不同的形态
// 4. 反过来思考, 要达到多态, 有两个条件, 一个是虚函数覆盖
// 一个是对象的指针或引用调用虚函数
// 5. 再通过下面的汇编代码分析, 看出满足多态的函数调用, 不是在编译时确定的
// 是运行起来以后到对象中找去的, 不满足多态的函数调用在编译时就已经确认好.

// 以下汇编代码中跟本问题不相关的都被去掉了
//void Func(Person* p)
//{
//...
//p->BuyTicket();
//// p 中存的是 mike对象的指针, 将 p 移动到 eax 中
//001940DE  mov         eax, dword ptr[p]
//// [eax]就是取 eax 值指向的内容, 这里相当于把 mike 对象头 4 个字节存的虚表指针移动到了 edx
//001940E1  mov         edx, dword ptr[eax]
//// [edx]就是取 edx 值指向的内容, 这里相当于把虚表中的头 4 个字节存的虚函数指针移动到了 eax
//00B823EE  mov         eax, dword ptr[edx]
//// call eax 中存储的虚函数的指针. 这里可以看出多态的调用, 不是在编译时确定的
//// 是运行起来以后到对象中找取的
//001940EA  call        eax
//}
//int main()
//{
//...
//// 首先 BuyTicket 虽然是虚函数, 但是 mike 是对象, 不满足多态的条件
//// 所以这里是普通函数的调用, 在编译时已经从符号表确认了函数的地址, 直接 call 地址
//mike.BuyTicket();
//00195182  lea         ecx, [mike]
//00195185  call        Person::BuyTicket(01914F6h)
//...
//}

// 4.3 动态绑定与静态绑定

// 1. 静态绑定又称为前期绑定 (早绑定), 在程序编译期间确定了程序的行为
// 也称为静态多态, 比如: 函数重载
// 2. 动态绑定又称后期绑定 (晚绑定), 是在程序运行期间
// 根据拿到的对象的类型确定程序的具体行为, 调用具体的函数, 也称为动态多态.
// 3. 上述汇编代码很好的解释了什么是静态 (编译器) 绑定和动态 (运行时) 绑定.

// 5. 单继承和多继承关系的虚函数表
// 关注在单继承和多继承关系中派生类对象的虚表模型
// 因为基类的虚表模型前面已经看过了, 没什么需要特别研究的

//class Base 
//{
//public:
//	virtual void func1() { cout << "Base::func1" << endl; }
//	virtual void func2() { cout << "Base::func2" << endl; }
//private:
//	int _a = 0;
//};
//
//class Derive : public Base {
//public:
//	virtual void func1() { cout << "Derive::func1" << endl; }
//	virtual void func3() { cout << "Derive::func3" << endl; }
//	virtual void func4() { cout << "Derive::func4" << endl; }
//private:
//	int _b = 1;
//};

//int main()
//{
//	Base b;
//	Derive d;
//
//	return 0;
//}

// 观察下图中的监视窗口中我们发现看不见 func3 和 func4
// 这里是编译器的监视窗口故意隐藏了这两个函数
// 也可以认为是它的一个小 bug. 那么我们如何查看 d 的虚表呢
// 下面我们使用代码打印出虚表中的函数地址

//typedef void(*VFPTR) ();
//using VFPTR = void(*)();
//void PrintVTable(VFPTR* vTable)
//{
//	// 依次取虚表中的虚函数指针打印并调用, 调用就可以看出存的是哪个函数的地址
//	cout << "虚表地址 : 0x" << vTable << endl;
//	for (int i = 0; vTable[i] != nullptr; ++i)
//	{
//		printf("第%d个虚函数地址 : 0x%p, ->", i, vTable[i]);
//		VFPTR f = vTable[i];
//		f();
//	}
//	cout << endl;
//}
//
//int main()
//{
//	Base b;
//	Derive d;
//
//	// 思路: 取出 b, d 对象的头 4 bytes, 就是虚表的指针 (虚表指针的类型为 void**)
//	// 前面我们说了虚函数表本质是一个存储虚函数指针的指针数组
//	// 这个数组最后面放了一个 nullptr
//	// 1. 先取 b 的地址, 强转成一个 int* 的指针
//	// 2. 再解引用取值, 就取到了 b 对象头 4 bytes 的值, 这个值就是指向虚表的指针的值
//	// 3. 再强转成 VFPTR*, 因为虚表就是一个存储 VFPTR 类型 (虚函数指针类型) 的数组
//	// 4. 虚表指针传递给 PrintVTable 进行打印虚表
//	// 5. 需要说明的是这个打印虚表的代码经常会崩溃, 因为编译器有时对虚表的处理不够干净
//	// 虚表最后面没有放 nullptr, 导致越界, 这是编译器的问题
//	// 只需要点击目录栏的 生成 - 清理解决方案, 再编译就好了
//
//	VFPTR* vTableb = (VFPTR*)(*(int*)&b);
//	PrintVTable(vTableb);
//	VFPTR* vTabled = (VFPTR*)(*(int*)&d);
//	PrintVTable(vTabled);
//	return 0;
//}

//虚表地址: 0x00CB9B34
//第0个虚函数地址 : 0x00CB12B7, ->Base::func1
//第1个虚函数地址 : 0x00CB1113, ->Base::func2
//
//虚表地址 : 0x00CB9B64
//第0个虚函数地址 : 0x00CB1244, ->Derive::func1
//第1个虚函数地址 : 0x00CB1113, ->Base::func2
//第2个虚函数地址 : 0x00CB1230, ->Derive::func3
//第3个虚函数地址 : 0x00CB116D, ->Derive::func4

// 5.2 多继承中的虚函数表

//class Base1 
//{
//public:
//	virtual void func1() { cout << "Base1::func1" << endl; }
//	virtual void func2() { cout << "Base1::func2" << endl; }
//private:
//	int b1 = 0;
//};
//
//class Base2
//{
//public:
//	virtual void func1() { cout << "Base2::func1" << endl; }
//	virtual void func2() { cout << "Base2::func2" << endl; }
//private:
//	int b2 = 1;
//};
//
//class Derive : public Base1, public Base2 
//{
//public:
//	virtual void func1() { cout << "Derive::func1" << endl; }
//	virtual void func3() { cout << "Derive::func3" << endl; }
//private:
//	int d1 = 2;
//};
//
////typedef void(*VFPTR) ();
//using VFPTR = void(*)();
//void PrintVTable(VFPTR vTable[])
//{
//	cout << "虚表地址 : 0x" << vTable << endl;
//	for (int i = 0; vTable[i] != nullptr; ++i)
//	{
//		printf("第%d个虚函数地址 : 0x%p, ->", i, vTable[i]);
//		VFPTR f = vTable[i];
//		f();
//	}
//	cout << endl;
//}
//
//int main()
//{
//	Derive d;
//	VFPTR* vTableb1 = (VFPTR*)(*(int*)&d);
//	PrintVTable(vTableb1);
//
//	VFPTR* vTableb2 = (VFPTR*)(*(int*)((char*)&d + sizeof(Base1)));
//	PrintVTable(vTableb2);
//	return 0;
//}

// 观察下图可以看出: 多继承派生类的未重写的虚函数地址放在第一个继承基类对象的虚函数表中

//虚表地址: 0x008B9B94
//第0个虚函数地址 : 0x008B1249, ->Derive::func1
//第1个虚函数地址 : 0x008B12F3, ->Base1::func2
//第2个虚函数地址 : 0x008B1235, ->Derive::func3
//
//虚表地址 : 0x008B9BA8
//第0个虚函数地址 : 0x008B136B, ->Derive::func1
//第1个虚函数地址 : 0x008B10B4, ->Base2::func2

// 注意: 为什么 Derive 重写 fun1 后, Base1 和 Base2 的虚函数表中 func1 的地址却不一样?
// 由于 Base1 和 Base2 在 Derive 对象中的内存布局不同, Derive::func1 要适配不同的基类指针调用
// 所以, Base1 和 Base2 的虚表中 func1 地址不同, 不过最终都会调用 Derive::func1 函数

// 多重继承时, 每个基类都有自己的虚表, 派生类重写的虚函数在不同基类虚表中的地址可能不同
// 这是为了保证在通过不同的基类指针调用虚函数时, 能正确定位到派生类中重写的函数, 并且适配不同基类指针的调用

// 5.3 菱形继承, 菱形虚拟继承
// 实际中不建议设计出菱形继承及菱形虚拟继承, 一方面太复杂容易出问题, 另一方面这样的
// 模型, 访问基类成员有一定性能损耗. 所以菱形继承, 菱形虚拟继承的虚表就不看了
// 也不需要研究清楚, 因为实际中很少使用.

// 菱形继承 + virtual
//class A
//{
//public:
//	virtual void func1()
//	{
//		cout << "A::func1" << endl;
//	}
//public:
//	int _a = 0;
//};
//
//class B : public A
//{
//public:
//	virtual void func1()
//	{
//		cout << "B::func1" << endl;
//	}
//
//	virtual void func2()
//	{
//		cout << "B::func2" << endl;
//	}
//public:
//	int _b = 0;
//};
//
//class C : public A
//{
//public:
//	virtual void func1()
//	{
//		cout << "C::func1" << endl;
//	}
//
//	virtual void func2()
//	{
//		cout << "C::func2" << endl;
//	}
//public:
//	int _c = 0;
//};
//
//class D : public B, public C
//{
//public:
//	virtual void func1()
//	{
//		cout << "D::func1" << endl;
//	}
//
//	virtual void func3()
//	{
//		cout << "D::func3" << endl;
//	}
//public:
//	int _d = 0;
//};
//
////typedef void(*VFPTR) ();
//using VFPTR = void(*)();
//void PrintVTable(VFPTR vTable[])
//{
//	cout << "虚表地址 : 0x" << vTable << endl;
//	for (int i = 0; vTable[i] != nullptr; ++i)
//	{
//		printf("第%d个虚函数地址 : 0x%p, ->", i, vTable[i]);
//		VFPTR f = vTable[i];
//		f();
//	}
//	cout << endl;
//}
//
//int main()
//{
//	D d;
//	d.B::_a = 1;
//	d.C::_a = 2;
//	d._b = 3;
//	d._c = 4;
//	d._d = 5;
//
//	VFPTR* vTableb1 = (VFPTR*)(*(void**)&d);
//	PrintVTable(vTableb1);
//
//	VFPTR* vTableb2 = (VFPTR*)(*(void**)((char*)&d + sizeof(B)));
//	PrintVTable(vTableb2);
//
//	return 0;
//}

//0x00F3FD44  9c ab 63 00 ? ? c.	B 虚表指针
//0x00F3FD48  01 00 00 00  ....
//0x00F3FD4C  03 00 00 00  ....
//0x00F3FD50  b0 ab 63 00 ? ? c.	C 虚表指针
//0x00F3FD54  02 00 00 00  ....
//0x00F3FD58  04 00 00 00  ....
//0x00F3FD5C  05 00 00 00  ....
//0x00F3FD60  cc cc cc cc ? ? ? ?

//虚表地址: 0x0063AB9C
//第0个虚函数地址 : 0x00631375, ->D::func1
//第1个虚函数地址 : 0x00631087, ->B::func2
//第2个虚函数地址 : 0x00631262, ->D::func3
//
//虚表地址 : 0x0063ABB0
//第0个虚函数地址 : 0x00631005, ->D::func1
//第1个虚函数地址 : 0x0063140B, ->C::func2

// 菱形虚拟继承 + virtual
//class A
//{
//public:
//	virtual void func1()
//	{
//		cout << "A::func1" << endl;
//	}
//public:
//	int _a = 0;
//};
//
//class B : virtual public A
//{
//public:
//	virtual void func1()
//	{
//		cout << "B::func1" << endl;
//	}
//
//	virtual void func2()
//	{
//		cout << "B::func2" << endl;
//	}
//public:
//	int _b = 0;
//};
//
//class C : virtual public A
//{
//public:
//	virtual void func1()
//	{
//		cout << "C::func1" << endl;
//	}
//
//	virtual void func2()
//	{
//		cout << "C::func2" << endl;
//	}
//public:
//	int _c = 0;
//};
//
//class D : public B, public C
//{
//public:
//	virtual void func1()
//	{
//		cout << "D::func1" << endl;
//	}
//
//	virtual void func3()
//	{
//		cout << "D::func3" << endl;
//	}
//public:
//	int _d = 0;
//};
//
////typedef void(*VFPTR) ();
//using VFPTR = void(*)();
//void PrintVTable(VFPTR vTable[])
//{
//	cout << "虚表地址 : 0x" << vTable << endl;
//	for (int i = 0; vTable[i] != nullptr; ++i)
//	{
//		printf("第%d个虚函数地址 : 0x%p, ->", i, vTable[i]);
//		VFPTR f = vTable[i];
//		f();
//	}
//	cout << endl;
//}
//
//int main()
//{
//	D d;
//	d.B::_a = 1;
//	d.C::_a = 2;
//	d._b = 3;
//	d._c = 4;
//	d._d = 5;
//
//	VFPTR* vTableb1 = (VFPTR*)(*(void**)&d);
//	PrintVTable(vTableb1);
//
//	VFPTR* vTableb2 = (VFPTR*)(*(void**)((char*)&d + 12));
//	PrintVTable(vTableb2);
//
//	VFPTR* vTableb3 = (VFPTR*)(*(void**)((char*)&d + 28));
//	PrintVTable(vTableb3);
//
//	return 0;
//}

//0x0097FC60  c4 ab 08 00 ? ? ..	B 虚表指针
//0x0097FC64  e8 ab 08 00 ? ? ..	B 虚基表指针
//0x0097FC68  03 00 00 00  ....		
//0x0097FC6C  d4 ab 08 00 ? ? ..	C 虚表指针
//0x0097FC70  f4 ab 08 00 ? ? ..	C 虚基表指针
//0x0097FC74  04 00 00 00  ....
//0x0097FC78  05 00 00 00  ....
//0x0097FC7C  e0 ab 08 00 ? ? ..	A 虚表指针
//0x0097FC80  02 00 00 00  ....
//0x0097FC84  cc cc cc cc ? ? ? ?

//虚表地址: 0x0008ABC4
//第0个虚函数地址 : 0x00081082, ->B::func2
//第1个虚函数地址 : 0x0008125D, ->D::func3
//
//虚表地址 : 0x0008ABD4
//第0个虚函数地址 : 0x00081406, ->C::func2
//
//虚表地址 : 0x0008ABE0
//第0个虚函数地址 : 0x00081370, ->D::func1

// 6. 继承和多态常见的面试问题
// 6.1 概念查考
// 
// 1. 下面哪种面向对象的方法可以让你变得富有 (A)
// A. 继承 B. 封装 C. 多态 D. 抽象
// 
// 2. (D) 是面向对象程序设计语言中的一种机制
// 这种机制实现了方法的定义与具体的对象无关
// 而对方法的调用则可以关联于具体的对象
// A. 继承 B. 模板 C. 对象的自身引用 D. 动态绑定
// 
// 3. 面向对象设计中的继承和组合, 下面说法错误的是? (C)
// A. 继承允许我们覆盖重写父类的实现细节
// 父类的实现对于子类是可见的
// 是一种静态复用, 也称为白盒复用
// B. 组合的对象不需要关心各自的实现细节
// 之间的关系是在运行时候才确定的
// 是一种动态复用, 也称为黑盒复用
// C. 优先使用继承, 而不是组合, 是面向对象设计的第二原则
// D. 继承可以使子类能自动继承父类的接口
// 但在设计模式中认为这是一种破坏了父类的封装性的表现
// 
// 4. 以下关于纯虚函数的说法, 正确的是 (A)
// A. 声明纯虚函数的类不能实例化对象 B. 声明纯虚函数的类是虚基类
// C. 子类必须实现基类的纯虚函数 D. 纯虚函数必须是空函数
// 
// 5. 关于虚函数的描述正确的是 (B)
// A. 派生类的虚函数与基类的虚函数具有不同的参数个数和类型 B. 内联函数不能是虚函数
// C. 派生类必须重新定义基类的虚函数 D. 虚函数可以是一个 static 型的函数
//
// 6. 关于虚表说法正确的是 (D)
// A. 一个类只能有一张虚表 (多继承会有多个虚表)
// B. 基类中有虚函数, 如果子类中没有重写基类的虚函数, 
// 此时子类与基类共用同一张虚表 (每个类有自己独立的虚表)
// C. 虚表是在运行期间动态生成的 (编译期间生成)
// D. 一个类的不同对象共享该类的虚表 
// 
// 7. 假设 A 类中有虚函数, B 继承自 A, B 重写 A 中的虚函数, 也没有定义任何虚函数, 则 (D)
// A. A 类对象的前 4 个字节存储虚表地址, B 类对象前 4 个字节不是虚表地址
// B. A 类对象和 B 类对象前 4 个字节存储的都是虚基表的地址
// C. A 类对象和 B 类对象前 4 个字节存储的虚表地址相同
// D. A 类和 B 类虚表中虚函数个数相同, 但 A 类和 B 类使用的不是同一张虚表

// 8. 下面程序输出结果是什么? (A)
// 
//class A 
//{
//public:
//	A(const char* s) { cout << s << endl; }
//	~A() {}
//};
//
//class B : virtual public A
//{
//public:
//	B(const char* s1, const char* s2) : A(s1) { cout << s2 << endl; }
//};
//
//class C : virtual public A
//{
//public:
//	C(const char* s1, const char* s2) : A(s1) { cout << s2 << endl; }
//};
//
//class D : public B, public C
//{
//public:
//	D(const char* s1, const char* s2, const char* s3, const char* s4) : B(s1, s2), C(s1, s3), A(s1)
//	{
//		cout << s4 << endl;
//	}
//};
//
//int main()
//{
//	D* p = new D("class A", "class B", "class C", "class D");
//	delete p;
//	return 0;
//}

// A. class A class B class C class D B. class D class B class C class A
// C. class D class C class B class A D. class A class C class B class D
//
// 虚基类构造函数优先: 若存在虚基类, 虚基类的构造函数会被最先调用, 并且只会调用一次.
// 不管有多少条继承路径指向虚基类, 最终派生类里仅存在一份虚基类的实例.
// 非虚基类构造函数按顺序调用: 非虚基类的构造函数会按照它们在派生类定义中继承出现的顺序依次调用
// 派生类自身构造函数最后调用: 最后调用派生类自身的构造函数
// 
// 9. 多继承中指针偏移问题? 下面说法正确的是 (C)
// 
//class Base1 { public:	int _b1 = 0; };
//class Base2 { public:	int _b2 = 1; };
//class Derive : public Base1, public Base2 { public:	int _d = 2; };
//int main() 
//{
//	Derive d;
//	Base1* p1 = &d;
//	Base2* p2 = &d;
//	Derive* p3 = &d;
//	return 0;
//}
// 
// A. p1 == p2 == p3 B. p1 < p2 < p3 C. p1 == p3 != p2 D. p1 != p2 != p3
// 
//// 10. 以下程序输出结果是什么 (B)
// 
//class A
//{
//public:
//	virtual void func(int val = 1) { std::cout << "A->" << val << std::endl; }
//	virtual void test() { func(); }
//};
//
//class B : public A
//{
//public:
//	void func(int val = 0) { std::cout << "B->" << val << std::endl; }
//};
//
//int main(int argc, char* argv[])
//{
//	B* p = new B;
//	p->test();
//	return 0;
//}
// 
// A. A->0 B. B->1 C. A->1 D. B->0 E. 编译出错 F. 以上都不正确
// 
// 输出 B->1 的原因是:
// 虚函数的动态绑定机制使得实际调用的是 B 类的 func 函数实现
// 而缺省参数的静态绑定机制使得使用的是 A 类中 func 函数的默认参数值 1
// 为了避免这种混淆, 建议在重写虚函数时不要重新定义缺省参数值

// 6.2 问答题
// 
// 1. 什么是多态? 答: 参考本节课件内容
// 2. 什么是重载, 重写(覆盖), 重定义(隐藏)? 答: 参考本节课件内容
// 3. 多态的实现原理? 答: 参考本节课件内容
// 4. inline 函数可以是虚函数吗?
// 答: 可以, 不过编译器就忽略 inline 属性
// 这个函数就不再是 inline, 因为虚函数地址要放到虚表中去.
// 5. 静态成员可以是虚函数吗? 
// 答: 不能, 因为静态成员函数没有 this 指针
// 使用类型::成员函数的调用方式无法访问虚函数表, 所以静态成员函数不能是虚函数.
// 6. 构造函数可以是虚函数吗?
// 答: 不能, 因为对象中的虚函数表指针是在构造函数初始化列表阶段才初始化的
// 7. 析构函数可以是虚函数吗? 什么场景下析构函数是虚函数?
// 答: 可以, 并且最好把基类的析构函数定义成虚函数, 参考本节课件内容
// 8. 对象访问普通函数快还是虚函数更快?
// 答: 如果是普通对象, 是一样快的
// 如果是指针对象或者是引用对象, 则调用的普通函数快
// 因为构成多态, 运行时调用虚函数需要到虚函数表中去查找其地址.
// 9. 虚函数表是在什么阶段生成的, 存在哪的
// 答: 虚函数表是在编译阶段就生成的, 一般情况下存在代码段.
// 10. C++ 菱形继承的问题? 虚继承的原理?
// 答: 参考继承课件. 注意这里不要把虚函数表和虚基表搞混了
// 11. 什么是抽象类? 抽象类的作用?
// 答: 参考 (3. 抽象类). 抽象类体现出了接口继承关系.